function MapController(jqViewPort, jqMap) {
	var self = this;

	this.view = new MapView(jqViewPort, jqMap);
	this.view.setWheelHander(function(x, y, delta) {
		self.onWheel(x, y, delta);
	});

	this.view.setDragHander({
		onDrag: function(dx, dy) {
			self.onDrag(dx, dy);
		}
	});
}

MapController.prototype.onWheel = function(x, y, delta) {
	this.view.zoomOn(x, y, 1 - delta / 15);
};

MapController.prototype.onDrag = function(dx, dy) {
	this.view.translate(dx, dy);
};

MapController.prototype.fitViewportWidth = function() {
	this.view.fitViewportWidth();
};

MapController.prototype.onReady = function(handler) {
	this.view.onReady(handler);
};

function MapView(jqViewPort, jqMap) {
	this.jqViewPort = jqViewPort;

	this.jqMap = jqMap;
	this.zoom = 1;
	this.onReadyListeners = [];
	this.initImgDim();

	this.translation = {
		x : 0,
		y : 0
	};
}

MapView.prototype.initImgDim = function() {
	this.init = false;
	this.width = this.jqViewPort.width();
	this.height = this.jqViewPort.height();

	var self = this;
	this.jqMap.ready(function() {
		self.width = $(this).width();
		self.height = $(this).height();
		
		self.init = true;
		$(self.onReadyListeners).each(function(index, listener) {
			listener();
		});
		self.onReadyListeners = null;

	});
};

MapView.prototype.setDragHander = function(handler) {
	new DragHelper(this.jqMap, handler).register();
};

MapView.prototype.onReady = function(handler) {
	if (this.init) {
		handler();
	} else {
		this.onReadyListeners.push(handler);
	}
};

MapView.prototype.fitViewportWidth = function() {
	var self = this;
	this.onReady(function() {
		self.setZoomAndUpdateView(self.jqViewPort.width() / self.width);
	});
};

MapView.prototype.setWheelHander = function(handler) {
	var self = this;
	this.jqViewPort.on('mousewheel', function(event, delta) {

		if (eventIsOnMap(event)) {
			var targetPos = $(event.target).offset();
			handler(event.pageX - targetPos.left, event.pageY - targetPos.top, delta);
		} else {
			handler(0, 0, delta);
		}

		return false;
	});

	function eventIsOnMap(event) {
		return event.target == self.jqMap.get(0);
	}
};

MapView.prototype.setZoomAndUpdateView = function(zoom) {
	this.zoom = zoom;
	this.zoomOn(0, 0, 1);
};

MapView.prototype.zoomOn = function(x, y, zoomAdjustment) {
	var oldZoom = this.zoom;

	var zRange = new Range(0.1, 2);
	this.zoom = zRange.getValueInRange(this.zoom * zoomAdjustment);

	var finalZoomAdjustment = this.zoom / oldZoom;

	var shiftX = x - x * finalZoomAdjustment;
	var shiftY = y - y * finalZoomAdjustment;

	this.translate(shiftX, shiftY);
	this.jqMap.width(this.width * this.zoom);
};

MapView.prototype.translate = function(dx, dy) {
	var pos = this.getPosition();
	var x = pos.x + dx;
	var y = pos.y + dy;

	var padding = 30;
	var xRange = new Range(-this.getWidth() + padding, +this.jqViewPort.width() - padding);
	var yRange = new Range(-this.getHeight() + padding, +this.jqViewPort.height() - padding);

	x = xRange.getValueInRange(x);
	y = yRange.getValueInRange(y);

	this.jqMap.css('margin-left', x);
	this.jqMap.css('margin-top', y);

	this.updatePosition(x, y);
};

MapView.prototype.getPosition = function() {
	var self = this;
	return {
		x : self.translation.x,
		y : self.translation.y
	};
};

MapView.prototype.updatePosition = function(x, y) {
	this.translation.x = x;
	this.translation.y = y;
};

MapView.prototype.getWidth = function() {
	return this.width * this.zoom;
};

MapView.prototype.getHeight = function() {
	return this.height * this.zoom;
};